/**
 * 
 */


import java.util.ArrayList;
import java.util.List;

/**
 * @author olle
 *
 */
public class BoardImpl implements Board 
{
	
	private CellType[][] board;
	
//	private ArrayList<Cord> boxes;
//	private ArrayList<Cord> goals;
	private Cord[] boxes;
	private Cord[] goals;
	private Cord player;
	
//	public BoardImpl( CellType[][] board, ArrayList<Cord> boxes, ArrayList<Cord> goals, Cord player )
	public BoardImpl( CellType[][] board, Cord[] boxes, Cord[] goals, Cord player )
	{
		this.board = board;
		this.boxes = boxes;
		this.goals = goals;
		this.player = player;
	}
	
	public BoardImpl( CellType[][] board )
	{
		this.board = board;
//		this.boxes = boxes;
//		this.goals = goals;
//		this.player = player;
		
		ArrayList<Cord> boxesArr = new ArrayList<Cord>();
		ArrayList<Cord> goalsArr = new ArrayList<Cord>();
		
        for ( int x = 0; x < board.length; x++ )
        {
        	for ( int y = 0; y < board[0].length; y++ )
        	{
        		if ( board[x][y] == CellType.Player || board[x][y] == CellType.PlayerOnGoalSquare )
        		{
        			player = new CordImpl( x, y );
        		}
        		
        		if ( board[x][y] == CellType.GoalSquare || board[x][y] == CellType.PlayerOnGoalSquare )
        		{
        			goalsArr.add( new CordImpl( x, y ) );
        		}
        		
        		if ( board[x][y] == CellType.Box || board[x][y] == CellType.BoxOnGoal )
        		{
        			boxesArr.add( new CordImpl( x, y ) );
        		}
        	}
        }
        
		boxes = new Cord[boxesArr.size()];
		for ( int i = 0; i < boxes.length; i++)
		{
			boxes[i] = boxesArr.get(i);
		}
		
		goals = new Cord[goalsArr.size()];
		for ( int i = 0; i < goals.length; i++)
		{
			goals[i] = goalsArr.get(i);
		}
        
//        this.boxes = (Cord[]) boxes.toArray();
//        this.goals = (Cord[]) goals.toArray();
        
//        System.out.println("fff");
		
	}

	/* (non-Javadoc)
	 * @see board.Board#getLegalMoves(board.Move, moverules.MoveRule)
	 */
	@Override
	public List<Move> getLegalMoves(Cord currentPosition, MoveRule moveRule) 
	{
		
		ArrayList<Move> newMoves = new ArrayList<Move>();
				
//		for ( Direction direction : Direction.getValues() )
		for ( Move.Direction direction : Move.Direction.values() )
		{
//			Move newPotentiallyLegalMove = getAdjacentMoveInDirection( currentPosition, direction );
			Move newPotentiallyLegalMove = new MoveImpl( currentPosition.doNewMove(direction), direction );
			
			if (    ( isPotentiallyLegalMove(newPotentiallyLegalMove) ) && 
					( moveRule.isLegalMove(this, newPotentiallyLegalMove, direction) ) )
//			else if ( moveRule.isLegalMove( this , currentPosition, direction) )
			{
//				newMoves.add( getAdjacentMoveInDirection( currentPosition, direction ) );
				newMoves.add(newPotentiallyLegalMove);
			}
		}
		
		return newMoves;
	}
	
	/**
	 * Kollar om ett drag aer potentiellt giltigt, ignorerar drag som hamnar utanfaor braeder t.ex. eller
	 * saadana som aer in i vaeggar.
	 */
	private boolean isPotentiallyLegalMove( Move move )
	{
		boolean exists = true;
		
		exists &= (getCellType(move) != CellType.Wall);

//		exists &= ( board[x][y] == Board.EmptyFloor ) || ( board[x][y] == Board.GoalSquare );
		
		exists &= existsCellAt(move);
		
		return exists;
	}
	
	@Override
	public boolean existsCellAt( Cord cord )
	{
		return existsCellAt( cord.getX(), cord.getY() );
	}
	
	@Override
	public boolean existsCellAt( int x, int y )
	{
		boolean exists = true;
		
		exists &= 0 <= x;
		exists &= x < board.length;
		
		exists &= 0 <= y;
		exists &= y < board[0].length;
		
		return exists;
	}

	/* (non-Javadoc)
	 * @see board.Board#getCellType(board.Move)
	 */
	@Override
	public CellType getCellType(Cord cord) 
	{
		if ( ! existsCellAt( cord ) )
		{
			throw new IllegalArgumentException("Koordinaterna faor givet move utanfaor braedet.");
		}
		
		return( board[ cord.getX() ][ cord.getY() ] );
	}

	/* (non-Javadoc)
	 * @see board.Board#getCellType(int, int)
	 */
	@Override
	public CellType getCellType(int x, int y) 
	{
		if ( ! existsCellAt( x, y ) )
		{
			throw new IllegalArgumentException("Koordinaterna faor givet move utanfaor braedet.");
		}
		
		return( board[ x ][ y] );
	}

	/* (non-Javadoc)
	 * @see board.Board#getAdjacentMoveInDirection(board.Move, board.Move.Direction)
	 */
//	@Override
//	public Move getAdjacentMoveInDirection(Move cord, Direction direction) 
//	{
//		return new CordImpl( cord.getX() + direction.getXDiff(), cord.getY() + direction.getYDiff() );
//	}

	@Override
	public VisitedCellHelper getVisitedBoardHelper() 
	{
		return new VisitedCellHelperImpl( board );
	}

	@Override
	public Cord[] getBoxes() 
	{
		return boxes;
	}

	@Override
	public Cord[] getGoals() 
	{
		return goals;
	}

	@Override
	public Cord getPlayer() 
	{
		return player;
	}
	
	private void setCellType( Cord cord, CellType type )
	{
//		System.out.println( "innan: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
		board[ cord.getX() ][ cord.getY() ] = type;
//		System.out.println( "efter: "+cord+ ", "+board[ cord.getX() ][ cord.getY() ] );
	}
	
	private CellType clearCurrentCell( Cord cord )
	{
        switch ( getCellType(cord) ) 
        {
	        case Player:
	        	return CellType.EmptyFloor;
	        	
	        case PlayerOnGoalSquare: 
	        	return CellType.GoalSquare;
	        	
	        default: throw new IllegalArgumentException( "bugg! "+cord + " :"+getCellType(cord) );
        }
	}
	
	public CellType[][] getBoard()
	{
		return board;
	}
	
	private CellType setNextToPlayer( Cord cord )
	{
        switch ( getCellType(cord) ) 
        {
	        case EmptyFloor: 
	        case Box: 
	        	
	        	return CellType.Player;
	        	
	        case GoalSquare: 
	        case BoxOnGoal: 
	        	return CellType.PlayerOnGoalSquare;
	        	
	        default: throw new IllegalArgumentException( "bugg! "+cord + " :"+getCellType(cord) );
        }
	}
	
	private CellType pushBox( Cord cord )
	{
        switch ( getCellType(cord) ) 
        {
	        case EmptyFloor: 
	        	return CellType.Box;
	        	
	        case GoalSquare: 
	        	return CellType.BoxOnGoal;
	        	
	        default: throw new IllegalArgumentException( "bugg! "+cord + " :"+getCellType(cord) );
        }
	}

	@Override
	public boolean doMovePlayer(Move.Direction direction) 
	{
//		System.out.println("doMovePlayer() direction= "+direction);

		Cord originalPlayer = player;
		Cord nextCord = new CordImpl( player.getX()+direction.getXDiff(), player.getY() + direction.getYDiff() );
		
//		System.out.println("player: "+originalPlayer);
//		System.out.println("nextCord: "+nextCord);
		
		if ( getCellType( nextCord ).isBox() )
		{
//			Move nextNextCord = new MoveImpl( nextCord, direction );
			Cord nextNextCord = new CordImpl( nextCord.getX()+direction.getXDiff(), nextCord.getY() + direction.getYDiff() );
			setCellType(nextNextCord, pushBox(nextNextCord));
			
			for ( Cord box : boxes )
			{
				boolean changed = false;
				if ( nextCord.equals(box) )
				{
					box.update(nextNextCord.getX(), nextNextCord.getY());
					changed = true;
				}
				if ( !changed )
				{
					throw new RuntimeException("flytta laada bugg!");
				}
			}
			
//			System.out.println("nextNextCord: "+nextNextCord);
		}
		

		
		setCellType(originalPlayer, clearCurrentCell(originalPlayer));
		
		setCellType(nextCord, setNextToPlayer(nextCord));
		
		
		this.player = new CordImpl( nextCord.getX(), nextCord.getY() );
		
		return true;
		
	}

	@Override
	public void printBoard() 
	{
        for ( int y = 0; y < board[0].length; y++ )
        {
        	for ( int x = 0; x < board.length; x++ )
        	{
        		System.out.print( board[ x ][ y ] );
        	}
        	System.out.print( "\n" );
        }
	}
	
	@Override
	public Board clone()
	{
//		return new BoardImpl( boxes.clone(),
//				goals.clone(),	(Cord) player.clone() );
//		return new BoardImpl( board.clone(),new ArrayList<Cord>(boxes),
//				new ArrayList<Cord>(goals), 
//				(Cord) player.clone() );
		CellType[][] newBoard = new CellType[board.length][board[0].length];
		
		for(int i=0; i<board.length; i++)
			  for(int j=0; j<board[i].length; j++)
			    newBoard[i][j]=board[i][j];
		
//		return new BoardImpl( newBoard,new ArrayList<Cord>(boxes),
//		new ArrayList<Cord>(goals), 
//		 new CordImpl( player.getX(), player.getY() ) );
		
		Cord[] newBoxes = new Cord[boxes.length];
		for ( int i = 0; i < boxes.length; i++)
		{
			newBoxes[i] = boxes[i].cloneCord();
		}
		
		Cord[] newGoals = new Cord[goals.length];
		for ( int i = 0; i < goals.length; i++)
		{
			newGoals[i] = goals[i].cloneCord();
		}
		
		return new BoardImpl( newBoard,newBoxes,newGoals, player.cloneCord() );
		
	}

}
